package com.zengqingfa.springboot.redis.sequence;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 参考：https://www.cnblogs.com/antonyhubei/p/16112938.html
 */
@Slf4j
@Service
public class CommonLocalCacheService {

    @Resource
    private RedisTemplate redisTemplate;
    /**
     * 锁对象
     */
    private static final Object LOCK = new Object();

    /**
     * 本地缓存redis自增序列，例如：key:value=20211104：【1-10000】
     */
    private static final Cache<String, Optional<RedisSequence>> REDIS_SEQUENCE_CACHE = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.DAYS)
            .build();

    /**
     * 获取带前缀的redis key
     *
     * @param prefix
     * @return
     */
    public String defaultGeneratorId(String prefix) {
        return this.generatorId(prefix, 0);
    }

    /**
     * 获取不带前缀的redis key
     *
     * @return
     */
    public String generatorIdWithoutPrefix() {
        return this.generatorId("", 0);
    }

    /**
     * 获取redis自增Key，
     * 分布式id生成规则：
     * 根据日期 ： 年+月+日 + 3位随机字符串 + 7位redis自增key 拼接业务前缀
     * 取redis key 为   20211109，每次取10000，本地缓存下来，后续如果达到最大值，往上递增10000
     * 【1-10000】  前缀prefix+ 20211109xxx0000001
     *
     * @param prefix        前缀
     * @param randStrLength 中间随机字符串的长度
     * @return
     */
    @SneakyThrows
    public String generatorId(String prefix, Integer randStrLength) {
        String sequenceKey = DateUtils.parseTime(LocalDateTime.now(), DateUtils.TimeFormat.SHORT_DATE_PATTERN_NONE);
        StringBuilder sequenceId = new StringBuilder(prefix + sequenceKey);
        // 如果达到最大批次，换一批
        synchronized (LOCK) {
            //缓存时长为当前时间+1天
            Optional<RedisSequence> redisSequenceOptional = getLocalRedisSequence(prefix, sequenceKey, 10000, DateUtils.addDays(DateUtils.now(), 1));
            // 如果缓存为空，则redis挂了,随机字段
            if (!redisSequenceOptional.isPresent()) {
                for (int i = 0; i < 10; i++) {
                    sequenceId.append(RandomUtils.nextInt(0, 10));
                }
                return sequenceId.toString();
            }
            RedisSequence redisSequence = redisSequenceOptional.get();
            if (redisSequence.getStartSequence().get() == redisSequence.getEndSequence().get()) {
                //如果值相等则失效,重新获取
                log.info("----------------------------invalidate---------start:{}, end:{}", redisSequence.getStartSequence(), redisSequence.getEndSequence());
                REDIS_SEQUENCE_CACHE.invalidate(sequenceKey);
                return generatorId(prefix + sequenceKey, randStrLength);
            }
            redisSequence.getStartSequence().getAndAdd(1L);
            REDIS_SEQUENCE_CACHE.put(prefix + sequenceKey, Optional.of(redisSequence));
            String sequence = String.format("%1$07d", redisSequence.getStartSequence().get());
            //中间3位随机数
            for (int i = 0; i < randStrLength; i++) {
                sequenceId.append(RandomUtils.nextInt(0, 10));
            }
            sequenceId.append(sequence);
        }
        log.info("sequenceId is {}", sequenceId.toString());
        return sequenceId.toString();
    }

    /**
     * 从本地缓存中取redisSequence
     *
     * @param sequenceKey
     * @return
     */
    private Optional<RedisSequence> getLocalRedisSequence(String prefix, String sequenceKey, int increment, Date expireTime) {
        try {
            return REDIS_SEQUENCE_CACHE.get(prefix + sequenceKey, () -> {
                RedisAtomicLong counter = new RedisAtomicLong(prefix.concat(sequenceKey), redisTemplate.getConnectionFactory());
                counter.expireAt(expireTime);
                long end = counter.addAndGet(increment);
                RedisSequence redisSequence = RedisSequence.builder().startSequence(new AtomicLong(end - increment)).endSequence(new AtomicLong(end)).build();
                log.info("----------------------------get---------start:{}, end:{}", redisSequence.getStartSequence(), redisSequence.getEndSequence());
                return Optional.of(redisSequence);
            });
        } catch (Exception e) {
            log.error("getLocalRedisSequence is errored, prefix:{},sequenceKey:{}, increment:{}", prefix,sequenceKey, increment, e);
        }
        return Optional.empty();
    }

}